/*
 *  Image.cpp
 *  EidoTron
 *
 *  Modified by Luca Di Franco on 23/03/11.
 *
 *
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above notice and this permission notice shall be included in all copies
 * or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * File for "Mipmapping" lesson of the OpenGL tutorial on
 * www.videotutorialsrock.com
 *
 */

#include "Image.h"

#include <SDL_opengl.h>
#include <iostream>
#include <fstream>

using namespace std;

namespace
{
	int toInt(const char* bytes)
	{
		return (int)(((unsigned char)bytes[3] << 24) | ((unsigned char)bytes[2] << 16) | ((unsigned char)bytes[1] << 8) | (unsigned char)bytes[0]);
	}
	
	short toShort(const char* bytes)
	{
		return (short)(((unsigned char)bytes[1] << 8) | (unsigned char)bytes[0]);
	}
	
	int readInt(ifstream &input)
	{
		char buffer[4];
		input.read(buffer, 4);
		return toInt(buffer);
	}
	
	short readShort(ifstream &input)
	{
		char buffer[2];
		input.read(buffer, 2);
		return toShort(buffer);
	}
	
	template<class T> class auto_array
	{
	private:
		T* array;
		mutable bool isReleased;
	public:
		explicit auto_array(T* array_ = NULL) : array(array_), isReleased(false) { }
		
		auto_array(const auto_array<T> &aarray)
		{
			array = aarray.array;
			isReleased = aarray.isReleased;
			aarray.isReleased = true;
		}
		
		~auto_array()
		{
			if (!isReleased && array != NULL)
				delete[] array;
		}
		
		T* get() const
		{
			return array;
		}
		
		T &operator*() const
		{
			return *array;
		}
		
		void operator=(const auto_array<T> &aarray)
		{
			if (!isReleased && array != NULL)
				delete[] array;
			array = aarray.array;
			isReleased = aarray.isReleased;
			aarray.isReleased = true;
		}
		
		T* operator->() const
		{
			return array;
		}
		
		T* release()
		{
			isReleased = true;
			return array;
		}
		
		void reset(T* array_ = NULL)
		{
			if (!isReleased && array != NULL)
				delete[] array;
			array = array_;
		}
		
		T* operator+(int i)
		{
			return array + i;
		}
		
		T &operator[](int i)
		{
			return array[i];
		}
	};
}

Image* Image::loadBMP(const char* fileName)
{
	ifstream input;
	input.open(fileName, ifstream::binary);
	if (input.fail())
	{
		cout << "File " << fileName << " not found!\nSYSTEM WILL EXIT";
		exit(EXIT_FAILURE);
	}
	char buffer[2];
	input.read(buffer, 2);
	if (!(buffer[0] == 'B' && buffer[1] == 'M'))
	{
		cout << "File " << fileName << " is not a bitmap file!\nSYSTEM WILL EXIT";
		exit(EXIT_FAILURE);
	}
	input.ignore(8);
	int dataOffset = readInt(input);
	int headerSize = readInt(input);
	int width;
	int height;
	switch(headerSize)
	{
		case 40:	// V3
			width = readInt(input);
			height = readInt(input);
			input.ignore(2);
			if (readShort(input) != 24)
			{
				cout << "File " << fileName << " is not a 24 bits per pixel image!\nSYSTEM WILL EXIT";
				exit(EXIT_FAILURE);
			}
			if (readShort(input) != 0)
			{
				cout << "File " << fileName << " is compressed!\nSYSTEM WILL EXIT";
				exit(EXIT_FAILURE);
			}
			break;
		case 12:	// OS/2 V1
			width = readShort(input);
			height = readShort(input);
			input.ignore(2);
			if (readShort(input) != 24)
			{
				cout << "File " << fileName << " is not a 24 bits per pixel image!\nSYSTEM WILL EXIT";
				exit(EXIT_FAILURE);
			}
			break;
		case 64:	// OS/2 V2
			cout << "File " << fileName << " --- Can't load OS/2 V2 bitmaps!\nSYSTEM WILL EXIT";
			exit(EXIT_FAILURE);
			break;
		case 108:	// Windows V4
			cout << "File " << fileName << " --- Can't load Windows V4 bitmaps!\nSYSTEM WILL EXIT";
			exit(EXIT_FAILURE);
			break;
		case 124:	// Windows V5
			cout << "File " << fileName << " --- Can't load Windows V5 bitmaps!\nSYSTEM WILL EXIT";
			exit(EXIT_FAILURE);
			break;
		default:
			cout << "File " << fileName << " --- Unknown bitmap format!\nSYSTEM WILL EXIT";
			exit(EXIT_FAILURE);
	}
	int bytesPerRow = ((width * 3 + 3) / 4) * 4 - (width * 3 % 4);
	int size = bytesPerRow * height;
	auto_array<char> pixels(new char[size]);
	input.seekg(dataOffset, ios_base::beg);
	input.read(pixels.get(), size);
	auto_array<char> pixels2(new char[width * height * 3]);
	for(int y = 0; y < height; y++)
		for(int x = 0; x < width; x++)
			for(int c = 0; c < 3; c++)
				pixels2[3 * (width * y + x) + c] = pixels[bytesPerRow * y + 3 * x + (2 - c)];	
	input.close();
	return new Image(pixels2.release(), width, height);
}

Image::Image(char* ps, int w, int h) : pixels(ps), width(w), height(h)
{
	mipmapLoaded = false;
}

Image::~Image()
{
	unloadMipmappedTexture();
	delete[] pixels;
}

void Image::loadMipmappedTexture()
{
	if (mipmapLoaded)
		return;
	glGenTextures(1, &textureId);
	glBindTexture(GL_TEXTURE_2D, textureId);
	gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels);
	mipmapLoaded = true;
}

void Image::unloadMipmappedTexture()
{
	if (!mipmapLoaded)
		return;
	glDeleteTextures(1, &textureId);
	mipmapLoaded = false;
}